namespace Resharper.Abstractions.Actions
{
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using JetBrains.Annotations;
  using JetBrains.Application;
  using JetBrains.ProjectModel;
  using JetBrains.ReSharper.Feature.Services.Bulbs;
  using JetBrains.ReSharper.Psi;
  using JetBrains.ReSharper.Psi.CSharp;
  using JetBrains.ReSharper.Psi.Tree;
  using JetBrains.ReSharper.Psi.Util;
  using JetBrains.TextControl;
  using Resharper.Abstractions.Shell;
  using Resharper.Abstractions.Solutions;

  /// <summary>
  ///   Defines the <see cref="ExecuteContext" /> class.
  /// </summary>
  public class ExecuteContext : ProviderContext
  {
    #region Constructors and Destructors

    /// <summary>
    ///   Initializes a new instance of the <see cref="ExecuteContext" /> class.
    /// </summary>
    /// <param name="provider"> The provider. </param>
    /// <param name="solution"> The solution. </param>
    /// <param name="tag"> The tag. </param>
    internal ExecuteContext([NotNull] IContextActionDataProvider provider, [NotNull] ISolution solution, [CanBeNull] object tag)
      : base(provider)
    {
      if (provider == null)
      {
        throw new ArgumentNullException("provider");
      }

      if (solution == null)
      {
        throw new ArgumentNullException("solution");
      }

      this.Solution = new Solution(solution);
      this.Tag = tag;
    }

    #endregion

    #region Public Properties

    /// <summary>
    ///   Gets the factory.
    /// </summary>
    [NotNull]
    public Factory Factory
    {
      get
      {
        var psiModule = this.GetPsiModule();
        Debug.Assert(psiModule != null, "psiModule != null");

        var factory = CSharpElementFactory.GetInstance(psiModule);
        Debug.Assert(factory != null, "factory != null");

        return new Factory(factory);
      }
    }

    /// <summary>
    ///   Gets the formatter.
    /// </summary>
    public CodeFormatter Formatter
    {
      get
      {
        var psiModule = this.GetPsiModule();
        Debug.Assert(psiModule != null, "psiModule != null");

        var languageType = psiModule.PsiLanguage;
        Debug.Assert(languageType != null, "languageType != null");

        return new CodeFormatter(this.Solution, languageType);
      }
    }

    /// <summary>
    ///   Gets the solution.
    /// </summary>
    [NotNull]
    public Solution Solution { get; private set; }

    /// <summary>
    ///   Gets or sets the tag.
    /// </summary>
    /// <value> The tag. </value>
    public object Tag { get; set; }

    #endregion

    #region Properties

    /// <summary>
    ///   Gets or sets the internal text control.
    /// </summary>
    /// <value> The internal text control. </value>
    [CanBeNull]
    internal ITextControl InternalTextControl { get; set; }

    #endregion

    #region Public Methods

    /// <summary>
    /// Gets the configuration.
    /// </summary>
    /// <param name="parameterName">Name of the parameter.</param>
    [CanBeNull]
    public object GetResharperSetting(string parameterName)
    {
      var psiModule = this.GetPsiModule();
      if (psiModule == null)
      {
        return null;
      }

      var psiServices = psiModule.GetPsiServices();

      var attributesSet = psiServices.CacheManager.GetModuleAttributes(psiModule);
      if (attributesSet == null)
      {
        return null;
      }

      foreach (var instance in attributesSet.GetAttributeInstances(true))
      {
        var attributeName = instance.GetClrName().ShortName;
        if (attributeName != "ReSharperSettingAttribute")
        {
          continue;
        }

        var nameParameter = instance.PositionParameter(0);
        if (nameParameter == null)
        {
          continue;
        }

        if (!nameParameter.IsConstant)
        {
          continue;
        }

        var name = nameParameter.ConstantValue.Value as string;
        if (name != parameterName)
        {
          continue;
        }

        var valueParameter = instance.PositionParameter(1);
        if (!valueParameter.IsConstant)
        {
          continue;
        }

        return valueParameter.ConstantValue.Value;
      }

      return null;
    }

    /// <summary>
    ///   Gets the module attribute int.
    /// </summary>
    /// <param name="parameterName"> Name of the parameter. </param>
    /// <param name="defaultValue"> if set to <c>true</c> [default value]. </param>
    /// <returns> Returns <c>true</c> , if successful, otherwise <c>false</c> . </returns>
    public bool GetResharperSettingBool(string parameterName, bool defaultValue)
    {
      var value = this.GetResharperSetting(parameterName);
      if (value == null)
      {
        return defaultValue;
      }

      try
      {
        return (bool)value;
      }
      catch
      {
        return defaultValue;
      }
    }

    /// <summary>
    ///   Gets the module attribute int.
    /// </summary>
    /// <param name="parameterName"> Name of the parameter. </param>
    /// <param name="defaultValue"> The default value. </param>
    /// <returns> Returns the module attribute int. </returns>
    public int GetResharperSettingInt(string parameterName, int defaultValue)
    {
      var value = this.GetResharperSetting(parameterName);
      if (value == null)
      {
        return defaultValue;
      }

      try
      {
        return (int)value;
      }
      catch
      {
        return defaultValue;
      }
    }

    /// <summary>
    ///   Gets the resharper setting string list.
    /// </summary>
    /// <param name="parameterName"> Name of the parameter. </param>
    /// <returns> Returns the resharper setting string list. </returns>
    public List<string> GetResharperSettingStringList(string parameterName)
    {
      var psiModule = this.GetPsiModule();
      if (psiModule == null)
      {
        return null;
      }

      var result = new List<String>();

      var psiServices = psiModule.GetPsiServices();

      var attributesSet = psiServices.CacheManager.GetModuleAttributes(psiModule);
      if (attributesSet == null)
      {
        return null;
      }

      foreach (var instance in attributesSet.GetAttributeInstances(true))
      {
        var attributeName = instance.GetClrName().ShortName;
        if (attributeName != "ReSharperSettingAttribute")
        {
          continue;
        }

        var nameParameter = instance.PositionParameter(0);
        if (nameParameter == null)
        {
          continue;
        }

        if (!nameParameter.IsConstant)
        {
          continue;
        }

        var name = nameParameter.ConstantValue.Value as string;
        if (name != parameterName)
        {
          continue;
        }

        var valueParameter = instance.PositionParameter(1);
        if (!valueParameter.IsConstant)
        {
          continue;
        }

        var value = valueParameter.ConstantValue.Value as string;
        if (value != null)
        {
          result.Add(value);
        }
      }

      return result;
    }

    #endregion

    #region Methods

    /// <summary>
    ///   Gets the name of the type element by CLR.
    /// </summary>
    /// <param name="typeName"> Name of the type. </param>
    /// <returns> Returns the type element by CLR name. </returns>
    [CanBeNull]
    internal ITypeDeclaration GetTypeByName([NotNull] string typeName)
    {
      if (typeName == null)
      {
        throw new ArgumentNullException("typeName");
      }

      Debug.Assert(this.Solution != null, "solution != null");

      var psiModule = this.GetPsiModule();
      if (psiModule == null)
      {
        return null;
      }

      var typeElement = TypeElementUtil.GetTypeElementByClrName(new ClrTypeName(typeName), psiModule);
      if (typeElement == null)
      {
        return null;
      }

      return typeElement.GetDeclarations().FirstOrDefault() as ITypeDeclaration;
    }

    /// <summary>
    ///   Gets the psi module.
    /// </summary>
    /// <returns> Returns the psi module. </returns>
    private IPsiModule GetPsiModule()
    {
      return this.InternalProvider.PsiModule;
    }

    #endregion
  }
}